use std::time::Duration;

use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventType};

use super::{Factor, WinRef, Window};

pub struct WinEvent<'a>(pub &'a NSEvent);
impl<'a> types::IntoEvt<Window, types::Mouse> for WinEvent<'a> {
	fn to_evt(&self, win: WinRef<'_>) -> Option<types::Mouse> {
		fn other(evt: &NSEvent) -> types::Button {
			let id = unsafe { evt.buttonNumber() };
			let id = id % 255;
			let id = id as u8;
			types::Button::Mouse(id)
		}
		let evt = self.0;
		let (button, phase) = match unsafe { self.0.r#type() } {
			NSEventType::LeftMouseDown => (types::Button::Left, types::Phase::Start),
			NSEventType::LeftMouseUp => (types::Button::Left, types::Phase::End),
			NSEventType::LeftMouseDragged => (types::Button::Left, types::Phase::Update),
			NSEventType::RightMouseDown => (types::Button::Right, types::Phase::Start),
			NSEventType::RightMouseUp => (types::Button::Right, types::Phase::End),
			NSEventType::RightMouseDragged => (types::Button::Right, types::Phase::Update),
			NSEventType::OtherMouseDown => (other(evt), types::Phase::Start),
			NSEventType::OtherMouseUp => (other(evt), types::Phase::End),
			NSEventType::OtherMouseDragged => (other(evt), types::Phase::Update),
			NSEventType::MouseMoved => (types::Button::Left, types::Phase::Update),
			_ => return None,
		};
		let point = {
			let point = unsafe { evt.locationInWindow() };
			let point = win.1.convertPoint_fromView(point, None);
			let factor = Factor::from(win.0);
			(factor.to_f32(point.x), factor.to_f32(point.y))
		};
		let metas = conv_metas(evt);
		let nanos = unsafe { self.0.timestamp() } * 1_000_000_000.;
		let time = Duration::from_nanos(nanos as _);
		Some(types::Mouse::new(button, phase, point, metas, time))
	}
}
impl<'a> types::IntoEvt<Window, types::Wheel> for WinEvent<'a> {
	fn to_evt(&self, win: WinRef<'_>) -> Option<types::Wheel> {
		let event_type = unsafe { self.0.r#type() };
		match event_type {
			NSEventType::ScrollWheel => {
				let (x, y) = unsafe { (self.0.scrollingDeltaX(), self.0.scrollingDeltaY()) };
				Some(if unsafe { self.0.hasPreciseScrollingDeltas() } {
					let factor = Factor::from(win.0);
					types::Wheel::Pixel(factor.to_f32(x), factor.to_f32(y), 0.)
				} else {
					types::Wheel::Line(x as f32, y as f32, 0.)
				})
			}
			_ => None,
		}
	}
}
impl<'a> types::IntoEvt<Window, types::Keyboard> for WinEvent<'a> {
	fn to_evt(&self, _win: WinRef<'_>) -> Option<types::Keyboard> {
		let evt = self.0;
		let state = match unsafe { evt.r#type() } {
			NSEventType::KeyDown => types::State::Down,
			NSEventType::KeyUp => types::State::Up,
			_ => return None,
		};
		let code = conv_code(evt);
		let key = conv_key(evt, code);
		let location = code.into();
		let metas = conv_metas(evt);
		let repeat = unsafe { evt.isARepeat() };
		let composing = false;
		Some(types::Keyboard::new(key, code, state, location, metas, repeat, composing))
	}
}
impl<'a> types::IntoEvt<Window, types::Touch> for WinEvent<'a> {
	fn to_evt(&self, _win: WinRef<'_>) -> Option<types::Touch> {
		None
	}
}
fn conv_metas(evt: &NSEvent) -> types::Metas {
	let mut metas = types::Metas::EMPTY;
	let flags = unsafe { evt.modifierFlags() };
	use NSEventModifierFlags as F;
	if flags.contains(F::NSEventModifierFlagShift) {
		metas |= types::Metas::SHIFT;
	}
	if flags.contains(F::NSEventModifierFlagControl) {
		metas |= types::Metas::CONTROL;
	}
	if flags.contains(F::NSEventModifierFlagOption) {
		metas |= types::Metas::ALT;
	}
	if flags.contains(F::NSEventModifierFlagCommand) {
		metas |= types::Metas::META;
	}
	metas
}
fn conv_key(evt: &NSEvent, code: types::Code) -> types::Key {
	if let Some(chars) = unsafe { evt.characters() } {
		return types::Key::Character(chars.to_string().into());
	}
	if let Some(chars) = unsafe { evt.charactersIgnoringModifiers() } {
		return types::Key::Character(chars.to_string().into());
	}
	code.into()
}
fn conv_code(evt: &NSEvent) -> types::Code {
	let scancode = unsafe { evt.keyCode() };
	use types::Code as KeyCode;
	match scancode {
		0x00 => KeyCode::KeyA,
		0x01 => KeyCode::KeyS,
		0x02 => KeyCode::KeyD,
		0x03 => KeyCode::KeyF,
		0x04 => KeyCode::KeyH,
		0x05 => KeyCode::KeyG,
		0x06 => KeyCode::KeyZ,
		0x07 => KeyCode::KeyX,
		0x08 => KeyCode::KeyC,
		0x09 => KeyCode::KeyV,
		// 0x0a => World 1,
		0x0b => KeyCode::KeyB,
		0x0c => KeyCode::KeyQ,
		0x0d => KeyCode::KeyW,
		0x0e => KeyCode::KeyE,
		0x0f => KeyCode::KeyR,
		0x10 => KeyCode::KeyY,
		0x11 => KeyCode::KeyT,
		0x12 => KeyCode::Digit1,
		0x13 => KeyCode::Digit2,
		0x14 => KeyCode::Digit3,
		0x15 => KeyCode::Digit4,
		0x16 => KeyCode::Digit6,
		0x17 => KeyCode::Digit5,
		0x18 => KeyCode::Equal,
		0x19 => KeyCode::Digit9,
		0x1a => KeyCode::Digit7,
		0x1b => KeyCode::Minus,
		0x1c => KeyCode::Digit8,
		0x1d => KeyCode::Digit0,
		0x1e => KeyCode::BracketRight,
		0x1f => KeyCode::KeyO,
		0x20 => KeyCode::KeyU,
		0x21 => KeyCode::BracketLeft,
		0x22 => KeyCode::KeyI,
		0x23 => KeyCode::KeyP,
		0x24 => KeyCode::Enter,
		0x25 => KeyCode::KeyL,
		0x26 => KeyCode::KeyJ,
		0x27 => KeyCode::Quote,
		0x28 => KeyCode::KeyK,
		0x29 => KeyCode::Semicolon,
		0x2a => KeyCode::Backslash,
		0x2b => KeyCode::Comma,
		0x2c => KeyCode::Slash,
		0x2d => KeyCode::KeyN,
		0x2e => KeyCode::KeyM,
		0x2f => KeyCode::Period,
		0x30 => KeyCode::Tab,
		0x31 => KeyCode::Space,
		0x32 => KeyCode::Backquote,
		0x33 => KeyCode::Backspace,
		// 0x34 => unknown,
		0x35 => KeyCode::Escape,
		0x36 => KeyCode::MetaRight,
		0x37 => KeyCode::MetaLeft,
		0x38 => KeyCode::ShiftLeft,
		0x39 => KeyCode::CapsLock,
		0x3a => KeyCode::AltLeft,
		0x3b => KeyCode::ControlLeft,
		0x3c => KeyCode::ShiftRight,
		0x3d => KeyCode::AltRight,
		0x3e => KeyCode::ControlRight,
		0x3f => KeyCode::Fn,
		0x40 => KeyCode::F17,
		0x41 => KeyCode::NumpadDecimal,
		// 0x42 -> unknown,
		0x43 => KeyCode::NumpadMultiply,
		// 0x44 => unknown,
		0x45 => KeyCode::NumpadAdd,
		// 0x46 => unknown,
		0x47 => KeyCode::NumLock,
		// 0x48 => KeyCode::NumpadClear,

		// TODO: (Artur) for me, kVK_VolumeUp is 0x48
		// macOS 10.11
		// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/
		// Versions/A/Headers/Events.h
		0x49 => KeyCode::AudioVolumeUp,
		0x4a => KeyCode::AudioVolumeDown,
		0x4b => KeyCode::NumpadDivide,
		0x4c => KeyCode::NumpadEnter,
		// 0x4d => unknown,
		0x4e => KeyCode::NumpadSubtract,
		0x4f => KeyCode::F18,
		0x50 => KeyCode::F19,
		0x51 => KeyCode::NumpadEqual,
		0x52 => KeyCode::Numpad0,
		0x53 => KeyCode::Numpad1,
		0x54 => KeyCode::Numpad2,
		0x55 => KeyCode::Numpad3,
		0x56 => KeyCode::Numpad4,
		0x57 => KeyCode::Numpad5,
		0x58 => KeyCode::Numpad6,
		0x59 => KeyCode::Numpad7,
		0x5a => KeyCode::F20,
		0x5b => KeyCode::Numpad8,
		0x5c => KeyCode::Numpad9,
		0x5d => KeyCode::IntlYen,
		// 0x5e => JIS Ro,
		// 0x5f => unknown,
		0x60 => KeyCode::F5,
		0x61 => KeyCode::F6,
		0x62 => KeyCode::F7,
		0x63 => KeyCode::F3,
		0x64 => KeyCode::F8,
		0x65 => KeyCode::F9,
		// 0x66 => JIS Eisuu (macOS),
		0x67 => KeyCode::F11,
		// 0x68 => JIS Kanna (macOS),
		0x69 => KeyCode::F13,
		0x6a => KeyCode::F16,
		0x6b => KeyCode::F14,
		// 0x6c => unknown,
		0x6d => KeyCode::F10,
		// 0x6e => unknown,
		0x6f => KeyCode::F12,
		// 0x70 => unknown,
		0x71 => KeyCode::F15,
		0x72 => KeyCode::Insert,
		0x73 => KeyCode::Home,
		0x74 => KeyCode::PageUp,
		0x75 => KeyCode::Delete,
		0x76 => KeyCode::F4,
		0x77 => KeyCode::End,
		0x78 => KeyCode::F2,
		0x79 => KeyCode::PageDown,
		0x7a => KeyCode::F1,
		0x7b => KeyCode::ArrowLeft,
		0x7c => KeyCode::ArrowRight,
		0x7d => KeyCode::ArrowDown,
		0x7e => KeyCode::ArrowUp,
		// 0x7f =>  unknown,

		// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
		// backquote (`) on Windows' US layout.
		0xa => KeyCode::Backquote,
		_ => KeyCode::Unidentified(scancode as _),
	}
}
